/** @file javaclass.c
 * File for javaclass lib.
 *
 * Copyright (C) 2007 
 *   Renan Mendes Carvalho <aitherios@gmail.com>
 *   Pedro Marinho <pedro7x@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include"javaclass.h"
#include"javaclass_int.h"
#include"engine.h"

/**
 * Reads a ClassFile structure from passed stream.
 * @param stream
 * @return the class_file read or NULL in case of errors
 */
class_file *
read_class_file (FILE* stream)
{
  class_file * result;
  int i;

  /* alloc the main struture */
  result = (class_file *)push_method_area(sizeof (class_file));
  
  /* initialize de pointers of the main structure */
  result->constant_pool=NULL;
  result->interfaces=NULL;
  result->fields=NULL;
  result->methods=NULL;
  result->attributes=NULL;
  
  /* magic */
  if(4 > freadinv ((u1*)&(result->magic), 1, 4, stream))
  {
    errormsg ("read_class_file", "read error", 1, "magic");
    exit(1);
  }
  /* minor_version */
  if(2 > freadinv ((u1*)&(result->minor_version), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "minor version");
    exit(1);
  }
  /* major_version */
  if(2 > freadinv ((u1*)&(result->major_version), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "major version");
    exit(1);
  }
  /* constant_pool_count */
  if(2 > freadinv ((u1*)&(result->constant_pool_count), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "constant_pool_count");
    exit(1);
  }
  /* constant_pool */
  result->constant_pool=(cp_info *)push_method_area((result->constant_pool_count - 1)*sizeof(cp_info));
  for (i=0; i < result->constant_pool_count - 1; i++)
  {
    /* initialize the tag variable and the pointers of the i element of cp */
    (result->constant_pool+i)->tag=(u1)0;
    (result->constant_pool+i)->info=NULL;
    (result->constant_pool+i)->utf8info=NULL;
    
    /* read the i element of cp */
    read_cp_info (stream, result->constant_pool+i);
  }
  /* access_flags */
  if(2 > freadinv ((u1*)&(result->access_flags), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "access_flags");
    exit(1);
  }
  /* this_class */
  if(2 > freadinv ((u1*)&(result->this_class), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "this_class");
    exit(1);
  }
  /* super_class */
  if(2 > freadinv ((u1*)&(result->super_class), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "super_class");
    exit(1);
  }
  /* interfaces_count */
  if(2 > freadinv ((u1*)&(result->interfaces_count), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "interfaces_count");
    exit(1);
  }
  /* interfaces */
  if(result->interfaces_count) {
    result->interfaces=(u2 *)push_method_area(result->interfaces_count*sizeof(u2));
    for (i = 0; i < result->interfaces_count; i++)
      if(2 > freadinv ((u1*)(result->interfaces+i), 1, 2, stream))
      {
        errormsg ("read_class_file", "read error", 1, "interfaces");
        exit(1);
      }
  }
  /* fields_count */
  if(2 > freadinv ((u1*)&(result->fields_count), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "fields_count");
    exit(1);
  }
  /* fields */
  if(result->fields_count) {
    result->fields=(field_info*)push_method_area(result->fields_count*sizeof(field_info));
    for (i = 0; i < result->fields_count; i++)
      {
        /* initialize the attribute pointer of the i field element */
        (result->fields+i)->attributes=NULL;
        
        /* read the i field element */
        read_field_info (stream, result->fields+i);
      }
      
  }
  /* methods_count */
  if(2 > freadinv ((u1*)&(result->methods_count), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "methods_count");
    exit(1);
  }
  /* methods */
  if(result->methods_count) {
    result->methods=(method_info*)push_method_area(result->methods_count*sizeof(method_info));
    for (i = 0; i < result->methods_count; i++)
      {
        /* initialize the attribute pointer of the i method element */
        (result->methods+i)->attributes=NULL;
        
        /* read the i method element */
        read_method_info (stream, result->methods+i);
      }
  }
  /* attributes_count */
  if(2 > freadinv ((u1*)&(result->attributes_count), 1, 2, stream))
  {
    errormsg ("read_class_file", "read error", 1, "attributes_count");
    exit(1);
  }
  /* attributes */
  if(result->attributes_count) {
    result->attributes=(attribute_info*)push_method_area(result->attributes_count*sizeof(attribute_info));
    for (i = 0; i < result->attributes_count; i++)
      {
        /* initialize the info pointer of the i attribute element */
        (result->attributes+i)->info=NULL;
        
        /* read the i attribute element */
        read_attribute_info (stream, result->attributes+i);
      }
  }

  return result;
}

/**
 * Reads a method info
 * @param stream
 * @param methodinfo
 * @return the methodinfo pointer or NULL in case of errors
 */
method_info *
read_method_info (FILE * stream, method_info * methodinfo)
{
  int i;

  /* alloc in NULL case */
  if (methodinfo == NULL)
    methodinfo = (method_info *)push_method_area(sizeof (method_info));
  /* access_flags */
  if(2 > freadinv ((u1*)&(methodinfo->access_flags), 1, 2, stream))
  {
    errormsg ("read_method_info", "read error", 1, "access_flags");
    exit(1);
  }
  /* name_index */
  if(2 > freadinv ((u1*)&(methodinfo->name_index), 1, 2, stream))
  {
    errormsg ("read_method_info", "read error", 1, "name_index");
    exit(1);
  }
  /* descriptor_index */
  if(2 > freadinv ((u1*)&(methodinfo->descriptor_index), 1, 2, stream))
  {
    errormsg ("read_method_info", "read error", 1, "descriptor_index");
    exit(1);
  }
  /* attributes_count */
  if(2 > freadinv ((u1*)&(methodinfo->attributes_count), 1, 2, stream))
  {
    errormsg ("read_method_info", "read error", 1, "attributes_count");
    exit(1);
  }
  /* attributes */
  if(methodinfo->attributes_count) {
    methodinfo->attributes=(attribute_info*)push_method_area(methodinfo->attributes_count*sizeof(attribute_info));
    for (i = 0; i < methodinfo->attributes_count; i++)
      {
        /* initialize the info pointer of the i attribute element */
        (methodinfo->attributes+i)->info=NULL;
        
        /* read the i attribute element */
        read_attribute_info (stream, methodinfo->attributes+i);
      }
  }

  return methodinfo;
}

/**
 * Reads a FieldInfo structure.
 * The parameter stream is used for reading and the referenced address for writing. If the address is NULL
 * a new one is allocated.
 * @param stream
 * @param fieldinfo
 * @return address to the structure or NULL in case of any error
 */
field_info *
read_field_info (FILE* stream, field_info * fieldinfo)
{
  int i;

  /* alloc in NULL case */
  if (NULL == fieldinfo)
    fieldinfo = (field_info *)push_method_area(sizeof (field_info));
  /* access_flags */
  if(2 > freadinv ((u1*)&(fieldinfo->access_flags), 1, 2, stream))
  {
    errormsg ("read_field_info", "read error", 1, "access_flags");
    exit(1);
  }
  /* name_index */
  if(2 > freadinv ((u1*)&(fieldinfo->name_index), 1, 2, stream))
  {
    errormsg ("read_field_info", "read error", 1, "name_index");
    exit(1);
  }
  /* descriptor_index */
  if(2 > freadinv ((u1*)&(fieldinfo->descriptor_index), 1, 2, stream))
  {
    errormsg ("read_field_info", "read error", 1, "descriptor_index");
    exit(1);
  }
  /* attributes_count */
  if(2 > freadinv ((u1*)&(fieldinfo->attributes_count), 1, 2, stream))
  {
    errormsg ("read_field_info", "read error", 1, "attributes_count");
    exit(1);
  }
  /* attributes */
  fieldinfo->attributes=(attribute_info*)push_method_area(fieldinfo->attributes_count*sizeof(attribute_info));
  for (i = 0; i < fieldinfo->attributes_count; i++)
    {
      /* initialize the info pointer of the i attribute element */
      (fieldinfo->attributes+i)->info=NULL;
      
      /* read the i attribute element */
      read_attribute_info (stream, fieldinfo->attributes+i);
    }

  return fieldinfo;
}

/**
 * Reads an attributeInfo structure.
 * The reading is made from the referenced stream in the referenced memory area. If the mem area is NULL
 * an area will be allocated.
 * @param stream
 * @param attributeinfo
 * @return memory address to the read structure or NULL in case of any error.
 */
attribute_info *
read_attribute_info (FILE* stream, attribute_info * attributeinfo)
{

  /* alloc in case of NULL */
  if (NULL == attributeinfo)
    attributeinfo = (attribute_info *)push_method_area(sizeof (attribute_info));
  /* attribute_name_index */
  if(2 > freadinv ((u1*)&(attributeinfo->attribute_name_index), 1, 2, stream))
  {
    errormsg ("read_attribute_info", "read error", 1, "attribute_name_index");
    exit(1);
  }
  /* attribute_length */
  if(4 > freadinv ((u1*)&(attributeinfo->attribute_length), 1, 4, stream))
  {
    errormsg ("read_attribute_info", "read error", 1, "attribute_length");
    exit(1);
  }
  /* info */
  attributeinfo->info=(u1 *)push_method_area(attributeinfo->attribute_length*sizeof(u1));
  if(fread (attributeinfo->info, 1, attributeinfo->attribute_length, stream) < attributeinfo->attribute_length)
  {
    errormsg ("read_attribute_info", "read error", 1, "info");
    return NULL;
  }
  
  return attributeinfo;
}

/**
 * Reads a structure cp_info type from passed stream.
 * @param stream where to read
 * @param cpinfo
 * @return the read structure or NULL in case of error
 */
cp_info *
read_cp_info (FILE* stream, cp_info * cpinfo)
{
  int bytes_to_read = 2;
  char constant[10];
  
  /* alloc in case of NULL */
  if (NULL == cpinfo)
    cpinfo = (cp_info *)push_method_area(sizeof (cp_info));
  /* read tag */
  if(1 > freadinv ((u1*)&(cpinfo->tag), 1, 1, stream))
  {
    errormsg ("read_cp_info", "read error", 1, "tag");
    exit(1);
  }
  /* depeding on tag read the rest of bytes */
  switch (cpinfo->tag)
    {
    case CONSTANT_Class:
      bytes_to_read = 2;
      break;
    case CONSTANT_Fieldref:
      bytes_to_read = 4;
      break;
    case CONSTANT_Methodref:
      bytes_to_read = 4;
      break;
    case CONSTANT_InterfaceMethodref:
      bytes_to_read = 4;
      break;
    case CONSTANT_String:
      bytes_to_read = 2;
      break;
    case CONSTANT_Integer:
      bytes_to_read = 4;
      break;
    case CONSTANT_Float:
      bytes_to_read = 4;
      break;
    case CONSTANT_Long:
      bytes_to_read = 8;
      break;
    case CONSTANT_Double:
      bytes_to_read = 8;
      break;
    case CONSTANT_NameAndType:
      bytes_to_read = 4;
      break;
    case CONSTANT_Utf8:
      bytes_to_read = 2;
      break;
    default:
      sprintf(constant,"%X",cpinfo->tag);
      errormsg ("read_cp_info", "invalid tag", 1, constant);
      return NULL;
    }
  /* alloc */
  cpinfo->info=(u1 *)push_method_area(bytes_to_read*sizeof(u1));
  /* read */
  if(freadinv((cpinfo)->info, 1, bytes_to_read, stream) < bytes_to_read)
  {
    errormsg ("read_cp_info", "read error", 1, "info");
    return NULL;
  }
  /* CONSTANT_Utf8 case */
  if (CONSTANT_Utf8 == cpinfo->tag)
    {
      bytes_to_read=0;
      memcpy (&bytes_to_read,cpinfo->info,2);
      
      /* alloc */
      cpinfo->utf8info=(u1 *)push_method_area(bytes_to_read*sizeof(u1));
      /* read */
      if(fread ((cpinfo)->utf8info, 1, bytes_to_read, stream) < bytes_to_read)
      {
        errormsg ("read_cp_info", "read error", 1, "info, utf8 case");
        return NULL;
      }
    }

  return cpinfo;
}

/**
 * Read nmemb of size bytes from stream to ptr in a inverse order
 * @param ptr
 * @param size
 * @param nmemb
 * @param stream
 * @return void
 */
size_t
  freadinv(u1 * ptr, size_t size, size_t nmemb, FILE * stream)
{
  int count;
  size_t read_units;
  u1 *ptr2;
  
  ptr2=NULL;
  ptr2=(u1 *)push_method_area(nmemb*size);
  
  /* read */
  read_units=fread(ptr2, size, nmemb, stream);
  if(read_units < nmemb)
  {
    errormsg ("freadinv", "read error", 1, "ptr2");
    exit(1);
  }
  
  /* Invert the order */
  for(count=0;nmemb*size>count;count++)
    *(ptr+count)=*(ptr2+(nmemb*size-count-1));
  
  pop_method_area(nmemb*size);
  
  return read_units;
}

/**
 * Print the information of a class_file.
 * This is the first version, prints raw data only
 * @param stream where to print
 * @param cf
 * @return void
 */
void
print_class_file (FILE * stream, class_file * cf)
{
  int i;
  int limit;
  
  /* Print Magic */
  fprintf (stream, "Magic='%#X'\n", cf->magic);
  /* Print Minor version */
  fprintf (stream, "Minor version='%d'\n", cf->minor_version);
  /* Print Major version */
  fprintf (stream, "Major version='%d'\n", cf->major_version);
  /* Print Constant pool count */
  fprintf (stream, "Constant pool count='%d'\n", cf->constant_pool_count);
  /* Print Constant pool */
  fprintf (stream, "Constant pool: \n");
  if(NULL!=cf->constant_pool)
    {
      limit = cf->constant_pool_count-1;
      for (i = 0; i < limit; i++)
      {
        fprintf (stream, " - item %i:\n", i);
        /* Print a Constant pool element */
        print_cp_info (stream, cf->constant_pool+i);
      }
    } else {
      fprintf (stream, "none\n");
    }
  /* Print Access flags */
  fprintf (stream, "Access flags='0X%04X'\n", cf->access_flags);
  /* Print This class */
  fprintf (stream, "This class='%d'\n", cf->this_class);
  /* Print Super class */
  fprintf (stream, "Super class='%d'\n", cf->super_class);
  /* Print Interfaces count */
  fprintf (stream, "Interfaces count='%d'\n", cf->interfaces_count);
  /* Print Interfaces */
  fprintf (stream, "Interfaces: \n");
  if(NULL!=cf->interfaces)
  {
    limit = cf->interfaces_count;
    for (i = 0; i < limit; i++)
      {
        /* Print Interface element */
        fprintf (stream, " - interface %i:", i);
        fprintf (stream, " : \t%X\n", (unsigned int)*( cf->interfaces + i));
      }
  } else {
    fprintf (stream, "none\n");
  }
  /* Print Fields count */
  fprintf (stream, "Fields count='%d'\n", cf->fields_count);
  /* Print Fields */
  fprintf (stream, "Fields: \n");
  if(NULL!=cf->fields)
  {
    limit = cf->fields_count;
    for (i = 0; i < limit; i++)
      {
        fprintf (stream, "- field %i:\n", i);
        /* Print Field element */
        print_field_info (stream, cf->fields + i);
      }
  } else {
    fprintf (stream, "none\n");
  }
  /* Print Methods count */
  fprintf (stream, "Methods count='%d'\n", cf->methods_count);
  /* Print Methods */
  fprintf (stream, "Methods: \n");
  if(NULL!=cf->methods)
  {
    limit = cf->methods_count;
    for (i = 0; i < limit; i++)
      {
        fprintf (stream, " - method %i:\n", i);
        /* Print Method element */
        print_method_info (stream, cf->methods + i);
      }
  } else {
    fprintf (stream, "none\n");
  }
  /* Print Attribute count */
  fprintf (stream, "Attributes count='%d'\n", cf->attributes_count);
  /* Print Attribute */
  fprintf (stream, "Attributes: \n");
  if(NULL!=cf->attributes)
  {
    limit = cf->attributes_count;
    for (i = 0; i < limit; i++)
      {
        fprintf (stream, " - attribute %i:\n", i);
        /* Print Attribute element */
        print_attribute_info (stream, cf->attributes + i);
      }
  } else {
    fprintf (stream, "none\n");
  }
  fprintf (stream, "\n");
}

/**
 * Print the information of a method structure.
 * @param stream where to print
 * @param mi
 * @return void
 */
void
print_method_info (FILE * stream, method_info * mi)
{
  int i;
  int limit;
  
  /* Print Access flags */
  fprintf (stream, "\t * access flags='0X%04X'\n", mi->access_flags);
  /* Print Name index */
  fprintf (stream, "\t * name index='%d'\n", mi->name_index);
  /* Print Descriptor index */
  fprintf (stream, "\t * descriptor index='%d'\n", mi->descriptor_index);
  /* Print Attribute count */
  fprintf (stream, "\t * attributes count='%d'\n", mi->attributes_count);
  /* Print Attributes */
  fprintf (stream, "\t * attributes: \n");
  if(NULL!=mi->attributes)
  {
    limit = mi->attributes_count;
    for (i = 0; i < limit; i++)
      {
        fprintf (stream, "\t * attibute  %d of method:\n", i);
        /* Print Attribute element */
        print_attribute_info (stream, mi->attributes + i);
      }
  }
}

/**
 * Print the information of a field structure.
 * @param stream where to print
 * @param fi
 * @return void
 */
void
print_field_info (FILE* stream, field_info * fi)
{
  int i;
  int limit;
  
  /* Print Access flags */
  fprintf (stream, "\taccess flags='%d'", fi->access_flags);
  /* Print Name index */
  fprintf (stream, "\tname index='%d'", fi->name_index);
  /* Print Descriptor index */
  fprintf (stream, "\tdescriptor index='%d'", fi->descriptor_index);
  /* Print Attributes count */
  fprintf (stream, "\tattributes count='%d'", fi->attributes_count);
  /* Print Attributes */
  fprintf (stream, "\tattributes: \n");
  limit = fi->attributes_count;
  for (i = 0; i < limit; i++)
    {
      fprintf (stream, "\t * attibute  %d of method:\n", i);
      /* Print Attribute element */
      print_attribute_info (stream, fi->attributes + i);
    }
}

/**
 * Print the information of a attribute structure.
 * @param stream where to print
 * @param ai
 * @return void
 */
void 
print_attribute_info (FILE* stream, attribute_info * ai)
{
  int i, limit;
  
  /* Print Attribute name index */
  fprintf (stream, "\tattribute name index='%d'\n", ai->attribute_name_index);
  /* Print Attribute lenght */
  fprintf (stream, "\tattribute length='%d'\n", ai->attribute_length);
  /* Print Info */
  fprintf (stream, "\tinfo: \n");
  if(NULL!=ai->info)
  {
    limit = ai->attribute_length;
    for (i = 0; i < limit; i++)
    {
      fprintf (stream, "\t (byte %d): 0X%01X\n", i, *(ai->info+i));
    }
  }
}

/**
 * Print the information of a constant pool structure.
 * @param stream where to print
 * @param ci
 * @return void
 */
void
print_cp_info (FILE* stream, cp_info * ci)
{
  int bytes_to_print;
  int i;
  
  /* Print Tag */
  fprintf (stream, "\t- tag='0X%01X'\n", ci->tag);
  switch (ci->tag)
    {
    case CONSTANT_Class:
      bytes_to_print = 2;
      break;
    case CONSTANT_Fieldref:
      bytes_to_print = 4;
      break;
    case CONSTANT_Methodref:
      bytes_to_print = 4;
      break;
    case CONSTANT_InterfaceMethodref:
      bytes_to_print = 4;
      break;
    case CONSTANT_String:
      bytes_to_print = 2;
      break;
    case CONSTANT_Integer:
      bytes_to_print = 4;
      break;
    case CONSTANT_Float:
      bytes_to_print = 4;
      break;
    case CONSTANT_Long:
      bytes_to_print = 8;
      break;
    case CONSTANT_Double:
      bytes_to_print = 8;
      break;
    case CONSTANT_NameAndType:
      bytes_to_print = 4;
      break;
    case CONSTANT_Utf8:
      bytes_to_print = 2;
      break;
    default:
      errormsg ("print_cp_info", "invalid tag", 0);
  }
  /* Verify if a UTF8 constant */
  if(CONSTANT_Utf8!=ci->tag) {
    /* Print Info */
    fprintf (stream, "\t- info: \n");
    for (i = 0; i < bytes_to_print; i++)
      fprintf (stream, "\t (byte %d): 0X%01X\n", i, (unsigned char) *(ci->info + i));
  } else {
    bytes_to_print=0;
    memcpy (&bytes_to_print,ci->info,2);
    
    /* Print UTF8 info */
    fprintf (stream, "\t- info: \n\tUTF8_info lenth='%d'",bytes_to_print);
    for (i = 0; i < bytes_to_print; i++)
      fprintf (stream, "\n\t(byte %d): '%c'", i, (unsigned char) *(ci->utf8info + i));
    fprintf (stream, "\n");
  }
}
